QRegalito

Cargando...

font-bold hover:bg-indigo-700">Reintentar`; this.icons(); }, connection(ok) { document.getElementById('connection-status').innerHTML = ok ? `● Conectado` : `● Sin conexión`; }, icons() { if (typeof lucide !== 'undefined') lucide.createIcons(); } }; const App = { api: null, sessionId: null, claimToken: null, async init() { this.api = new ApiClient(Config.API_URL); UI.init(); UI.icons(); try { await this.api.health(); UI.connection(true); } catch { UI.connection(false); UI.error('No se puede conectar al servidor'); return; } const payCode = Utils.getUrlParam('pay'); const claimCode = Utils.getUrlParam('claim'); const status = Utils.getUrlParam('status'); if (payCode && status === 'success') { this.handlePaymentReturn(payCode); } else if (payCode) { this.loadPayMode(payCode); } else if (claimCode) { this.loadClaimMode(claimCode); } else { this.showCreate(); } }, goHome() { Utils.clearUrlParams(); this.sessionId = null; this.claimToken = null; this.showCreate(); }, showCreate() { UI.setHeader('default'); UI.status.textContent = 'Crear QRegalito'; UI.app.innerHTML = `

Preparar Regalo

Completá los datos y generá el QR

$

$${Utils.formatMoney(Config.MIN_AMOUNT)} - $${Utils.formatMoney(Config.MAX_AMOUNT)}

⚠️ Se deducirá un 10% en concepto de impuestos y comisiones.

Compartí esta clave con quien reciba el regalo

`; UI.icons(); }, async createGift() { const name = document.getElementById('inName').value.trim(); const amount = document.getElementById('inAmount').value; const pin = document.getElementById('inPin').value; const note = document.getElementById('inNote').value.trim(); const btn = document.getElementById('btnCreate'); if (!name || name.length < 2) return UI.toast('Escribí tu nombre', true); if (!Utils.isValidAmount(amount)) return UI.toast('Monto inválido', true); if (!Utils.isValidPin(pin)) return UI.toast(`La clave debe ser ${Config.PIN_LENGTH} dígitos`, true); btn.disabled = true; btn.innerHTML = 'Generando...'; try { const result = await this.api.createGift({ senderName: name, amount: parseFloat(amount), pin, note }); this.sessionId = result.sessionId; this.showPaymentQR(result); } catch (e) { UI.toast(e.message, true); btn.disabled = false; btn.innerHTML = 'Generar QR de Pago'; UI.icons(); } }, showPaymentQR(data) { UI.setHeader('pending'); UI.status.textContent = 'Esperando pago'; const payUrl = `${Config.FRONTEND_URL}?pay=${data.qrCodePay}`; UI.app.innerHTML = `

QR de Pago

Escaneá para pagar $${Utils.formatMoney(data.amount)}

Código: ${data.qrCodePay}

Pagar con Mercado Pago
`; UI.icons(); Utils.renderQR('paymentQR', payUrl, 192); }, async checkPaymentStatus(sessionId) { UI.loader('Verificando pago...'); try { const result = await this.api.checkPayment(sessionId); if (result.paid) { this.showPaymentSuccess(sessionId, result.qrCodeClaim, result.amount); } else { UI.toast('Pago no confirmado aún', true); this.showPaymentQR({ sessionId, qrCodePay: result.qrCodePay, paymentUrl: result.paymentUrl, amount: result.amount }); } } catch (e) { UI.toast(e.message, true); } }, async handlePaymentReturn(payCode) { UI.loader('Verificando pago...'); try { const gift = await this.api.getByPayCode(payCode); if (gift.status === 'FUNDED' || gift.status === 'PENDING_PAYOUT' || gift.status === 'COMPLETED') { Utils.setUrlParam('status', null); this.showPaymentSuccess(gift.sessionId, gift.qrCodeClaim, gift.amount); } else { this.loadPayMode(payCode); } } catch (e) { UI.error(e.message); } }, showPaymentSuccess(sessionId, qrCodeClaim, amount) { UI.setHeader('success'); UI.status.textContent = '¡Pago confirmado!'; UI.confetti(); const claimUrl = `${Config.FRONTEND_URL}?claim=${qrCodeClaim}`; UI.app.innerHTML = `

¡Regalo Listo!

Compartí este QR para que lo cobren

QR DE COBRO

Código: ${qrCodeClaim}

Link para compartir:

⚠️ Recordá compartir también la clave de ${Config.PIN_LENGTH} dígitos

`; UI.icons(); Utils.renderQR('claimQR', claimUrl, 192); }, async downloadClaimQR(code) { const url = `${Config.FRONTEND_URL}?claim=${code}`; const dataUrl = await Utils.generateQR(url); if (dataUrl) { const a = document.createElement('a'); a.download = `qregalito-cobro-${code}.png`; a.href = dataUrl; a.click(); } }, async loadPayMode(code) { UI.loader('Cargando...'); try { const gift = await this.api.getByPayCode(code); if (gift.status === 'NOT_FOUND') return UI.error('Regalo no encontrado'); if (gift.status === 'EXPIRED') return UI.error('Este regalo ha expirado'); if (gift.status === 'FUNDED' || gift.status === 'PENDING_PAYOUT' || gift.status === 'COMPLETED') return this.showPaymentSuccess(gift.sessionId, gift.qrCodeClaim, gift.amount); this.sessionId = gift.sessionId; this.showPaymentQR({ sessionId: gift.sessionId, qrCodePay: code, paymentUrl: gift.paymentUrl, amount: gift.amount }); } catch (e) { UI.error(e.message); } }, async loadClaimMode(code) { UI.loader('Cargando regalo...'); try { const gift = await this.api.getByClaimCode(code); if (gift.status === 'NOT_FOUND') return UI.error('Regalo no encontrado'); if (gift.status === 'LOCKED') return this.showLocked(gift.message, gift.retryAfter); if (gift.status === 'PENDING_PAYMENT') return this.showPendingPayment(gift); if (gift.status === 'PENDING_PAYOUT') return this.showAlreadyClaimed(); if (gift.status === 'COMPLETED') return this.showCompleted(); if (gift.status === 'EXPIRED') return this.showExpired(); this.sessionId = gift.sessionId; this.showUnlock(gift); } catch (e) { UI.error(e.message); } }, showPendingPayment(gift) { UI.setHeader('pending'); UI.status.textContent = 'Esperando pago'; UI.app.innerHTML = `

Esperando pago

${Utils.escapeHtml(gift.senderName)} todavía no completó el pago.

`; UI.icons(); }, showExpired() { UI.setHeader('default'); UI.status.textContent = 'Expirado'; UI.app.innerHTML = `

Expirado

Este regalo ya no está disponible.

`; UI.icons(); }, showAlreadyClaimed() { UI.setHeader('pending'); UI.status.textContent = 'En proceso'; UI.app.innerHTML = `

Solicitud en proceso

Ya solicitaste el cobro de este regalo.

El dinero llegará pronto.

`; UI.icons(); }, showCompleted() { UI.setHeader('success'); UI.status.textContent = 'Completado'; UI.app.innerHTML = `

¡Completado!

Este regalo ya fue cobrado.

`; UI.icons(); }, showLocked(msg, retryAfter) { UI.setHeader('default'); UI.status.textContent = 'Bloqueado'; UI.app.innerHTML = `

Bloqueado

${Utils.escapeHtml(msg)}

`; UI.icons(); if (retryAfter > 0) { let rem = retryAfter; const tick = () => { if (rem <= 0) return location.reload(); document.getElementById('countdown').textContent = `Intentá en ${Math.floor(rem/60)}:${(rem%60).toString().padStart(2,'0')}`; rem--; setTimeout(tick, 1000); }; tick(); } }, showUnlock(gift) { UI.setHeader('default'); UI.status.textContent = '¡Tenés un regalo!'; const netAmount = gift.amount * (1 - Config.COMMISSION_RATE); UI.app.innerHTML = `

${Utils.escapeHtml(gift.senderName)} te envió

$${Utils.formatMoney(netAmount)}

(Original: $${Utils.formatMoney(gift.amount)} - 10% comisión)

${gift.senderNote ? `

"${Utils.escapeHtml(gift.senderNote)}"

` : ''}
`; UI.icons(); setTimeout(() => document.getElementById('inUnlockPin')?.focus(), 100); }, async verifyPin() { const pinInput = document.getElementById('inUnlockPin'); const pin = pinInput.value; const btn = document.getElementById('btnUnlock'); if (!Utils.isValidPin(pin)) return UI.toast(`Ingresá ${Config.PIN_LENGTH} dígitos`, true); btn.disabled = true; btn.innerHTML = 'Verificando...'; try { const result = await this.api.verifyPin(this.sessionId, pin); this.claimToken = result.claimToken; UI.toast('¡Clave correcta!'); this.showClaimForm(result); } catch (e) { UI.toast(e.message, true); pinInput.classList.add('shake', 'border-red-500'); setTimeout(() => pinInput.classList.remove('shake', 'border-red-500'), 500); btn.disabled = false; btn.innerHTML = 'Desbloquear'; pinInput.value = ''; pinInput.focus(); if (e.message.toLowerCase().includes('bloqueado')) setTimeout(() => location.reload(), 2000); } }, showClaimForm(data) { UI.setHeader('success'); UI.status.textContent = '¡Desbloqueado!'; UI.app.innerHTML = `

Ingresá tus datos

Para recibir $${Utils.formatMoney(data.netAmount)}

`; UI.icons(); setTimeout(() => document.getElementById('inAlias')?.focus(), 100); }, async submitClaim() { const alias = document.getElementById('inAlias').value.trim(); const name = document.getElementById('inReceiverName').value.trim(); const dni = document.getElementById('inDni').value.trim(); const email = document.getElementById('inEmail').value.trim(); const phone = document.getElementById('inPhone').value.trim(); const btn = document.getElementById('btnClaim'); if (alias.length < 6) return UI.toast('Ingresá un alias o CBU/CVU válido', true); if (name.length < 2) return UI.toast('Ingresá tu nombre completo', true); if (dni.length < 7) return UI.toast('Ingresá tu DNI', true); btn.disabled = true; btn.innerHTML = 'Procesando...'; try { await this.api.claim(this.sessionId, this.claimToken, { alias, name, dni, email, phone }); UI.confetti(); this.showClaimSuccess(); } catch (e) { UI.toast(e.message, true); btn.disabled = false; btn.innerHTML = 'Solicitar cobro'; UI.icons(); if (e.message.toLowerCase().includes('token')) setTimeout(() => location.reload(), 2000); } }, showClaimSuccess() { UI.setHeader('success'); UI.status.textContent = '¡Solicitud enviada!'; UI.app.innerHTML = `

¡Felicitaciones!

Tu solicitud fue registrada.

El dinero llegará a tu cuenta pronto.

Procesamos las transferencias en horario bancario. Puede demorar hasta 24hs hábiles.

`; UI.icons(); } }; document.addEventListener('DOMContentLoaded', () => { const waitDeps = (cb, max = 50) => { let i = 0; const check = () => { if (typeof QRCode !== 'undefined' && typeof lucide !== 'undefined') cb(); else if (++i < max) setTimeout(check, 100); else cb(); }; check(); }; waitDeps(() => App.init()); });